﻿//	Copyright (c) 2012 Jonathan Loe
//
//	MIT license (http://en.wikipedia.org/wiki/MIT_License)
//
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights 
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
//	of the Software, and to permit persons to whom the Software is furnished to do so, 
//	subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in all 
//	copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
//	PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
//	FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Text;

namespace Ordinaire
{
    /// <summary>
    /// Serves as a helper that provides common functions related to Ordinaire.Data.TreeData.
    /// </summary>
    /// <typeparam name="TId">type of id and parent id</typeparam>
    /// <typeparam name="TEntity">table representation type</typeparam>
    public class TreeDataEntity<TId, TEntity> : DataAccessEntity, IHierarchy<TId>
        where TEntity : IHierarchy<TId>
    {
        #region Variables

        /// <summary>
        /// Tree node id.
        /// </summary>
        private TId id;

        /// <summary>
        /// Tree node parent id.
        /// </summary>
        private TId parentId;

        #endregion

        #region Constructor

        /// <summary>
        /// Returns new instance of HierarchyHelper.
        /// </summary>
        public TreeDataEntity() { }

        #endregion

        #region Property methods

        /// <summary>
        /// Gets or sets the tree node id.
        /// </summary>
        public TId Id
        {
            get { return this.id; }
            set { this.id = value; }
        }

        /// <summary>
        /// Gets or sets the tree node parent id.
        /// </summary>
        public TId ParentId
        {
            get { return this.parentId; }
            set { this.parentId = value; }
        }

        #endregion

        /// <summary>
        /// Returns a value indicating whether data already exists with specified id and parent id.
        /// </summary>
        /// <param name="id">node id</param>
        /// <param name="parentId">parent node id</param>
        /// <returns>true if already exists, else false</returns>
        public bool Exists(TId id, TId parentId)
        {
            if (default(TId).Equals(id))
            {
                throw new ArgumentNullException("id");
            }

            var find = from h in Context.GetTable(typeof(TEntity)).Cast<IHierarchy<TId>>()
                       where h.Id.Equals(id) && h.ParentId.Equals(parentId)
                       select h;
            IHierarchy<TId> result = find.FirstOrDefault();
            return (result != null) ? true : false;
        }

        /// <summary>
        /// Returns the node data with the specified id.
        /// </summary>
        /// <param name="id">node ide</param>
        /// <returns>node data with the specified id</returns>
        public TEntity GetNode(TId id)
        {
            if (default(TId).Equals(id))
            {
                throw new ArgumentNullException("id");
            }

            var getNode = from r in Context.GetTable(typeof(TEntity)).Cast<TEntity>()
                          where r.Id.Equals(id)
                          select r;
            return getNode.SingleOrDefault();
        }

        /// <summary>
        /// Returns all root nodes.
        /// </summary>
        /// <returns>list of root nodes</returns>
        public List<TEntity> GetRoots()
        {
            // T default value
            TId rootParentValue = default(TId);

            // Set other default value, other than default(T)
            if (typeof(TId) == typeof(int))
            {
                rootParentValue = (TId)(object)0;
            }
            else if (typeof(TId) == typeof(string))
            {
                rootParentValue = (TId)(object)String.Empty;
            }
            else
            {
                throw ExceptionFactory.Create<TypeNotSupportedException>(ExceptionMessage.TypeNotSupported, typeof(TId).FullName);
            }

            var getRoots = from t in Context.GetTable(typeof(TEntity)).Cast<TEntity>()
                           where t.ParentId.Equals(default(TId)) || t.ParentId.Equals(rootParentValue) 
                           select t;
            return getRoots.ToList();
        }

        /// <summary>
        /// Returns list of child nodes of the specified node id, up to the specified depth.
        /// </summary>
        /// <param name="id">node id</param>
        /// <param name="depth">level depth</param>
        /// <returns>list of child nodes</returns>
        public List<TEntity> GetChildren(TId id, int depth = 1)
        {
            if (default(TId).Equals(id))
            {
                throw new ArgumentNullException("id");
            }

            List<TId> ids        = new List<TId>(); // To store children ids
            List<TEntity> result = new List<TEntity>(); // To store childrens found

            ids.Add(id);
            for (int i = 0; i < depth; i++)
            {
                // no more children
                if (ids.Count == 0)
                {
                    break;
                }

                foreach (TId tmpId in ids)
                {
                    var getChildren = from t in Context.GetTable(typeof(TEntity)).Cast<TEntity>()
                                      where t.ParentId.Equals(tmpId)
                                      select t;
                    result.AddRange(getChildren.ToList());
                }

                // Gather returned ids for next depth
                if ((i + 1) < depth)
                {
                    ids.Clear();
                    foreach (TEntity data in result)
                    {
                        ids.Add(data.Id);
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Returns list of parent nodes of specified node id, up to specified level.
        /// </summary>
        /// <param name="id">node id</param>
        /// <param name="level">how far is the parent level</param>
        /// <returns>list of parent nodes</returns>
        public List<TEntity> GetParents(TId id, int level = 1)
        {
            if (default(TId).Equals(id))
            {
                throw new ArgumentNullException("id");
            }

            // T default value
            TId rootDefaultValue = default(TId);

            // Set other default value, other than default(T)
            if (typeof(TId) == typeof(int))
            {
                rootDefaultValue = (TId)(object)0;
            }
            else if (typeof(TId) == typeof(string))
            {
                rootDefaultValue = (TId)(object)String.Empty;
            }
            else
            {
                throw ExceptionFactory.Create<TypeNotSupportedException>(ExceptionMessage.TypeNotSupported, typeof(TId).FullName);
            }

            List<TEntity> result = new List<TEntity>(); // To store parents found

            // Obtain node's parent id
            var getParentId = from t in Context.GetTable(typeof(TEntity)).Cast<TEntity>()
                              where t.Id.Equals(id)
                              select t.ParentId;
            TId parentId = getParentId.SingleOrDefault();
            for (int i = 0; i < level; i++)
            {
                // node is an orphan, quit
                if (parentId.Equals(rootDefaultValue) || parentId.Equals(default(TId)))
                {
                    break;
                }

                // Get parent's node
                var getNode = from t in Context.GetTable(typeof(TEntity)).Cast<TEntity>()
                              where t.Id.Equals(parentId)
                              select t;
                TEntity node = getNode.SingleOrDefault();
                result.Add(node);

                // Prepare ids for next query
                if ((i + 1) < level)
                {
                    parentId = node.ParentId;
                    // no more parent
                    if (parentId.Equals(rootDefaultValue) || parentId.Equals(default(TId)))
                    {
                        break;
                    }
                }
            }

            return result;
        }

    }
}
